package edu.vt.ece.rt.dstm;

import java.util.concurrent.atomic.AtomicInteger;

import edu.vt.ece.rt.quorum.Cluster;
import edu.vt.ece.rt.quorum.ClustersManager;
import edu.vt.ece.rt.quorum.Resource;
import edu.vt.ece.rt.quorum.quorum.QuroumFailureException;
import edu.vt.ece.rt.quorum.quorum.ReadQuorum;
import edu.vt.ece.rt.quorum.quorum.WriteQuorum;
import edu.vt.ece.rt.quorum.util.ReplyObjectStore;
import edu.vt.ece.rt.util.Logger.Logger;


/**
 * Application Interface to DSTM
 * @author Sudhanshu
 */
public class TMProxy{
	ReadQuorum rQ;
	WriteQuorum wQ;
	AtomicInteger requestReconfig; 
	
	static TMProxy tp;
	
	public static TMProxy getTMProxy(){
		if(tp == null){
			tp = new TMProxy();
			tp.config();
		}
		return tp;
	}
	
	public static void start(){
		if(tp == null){
			tp = new TMProxy();
			tp.config();
		}
	}
	
	/*
	 * Initial TMProxy Should be called after finding the rQ & wQ for node
	 */
	public void config(){
		Logger.debug("Initiating the TMProxy");
//		ClustersManager.calculateDistance();
		try {
			Thread.sleep((10000L)* (ClustersManager.baseClusterId));
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		getTMProxy().wQ = WriteQuorum.getQuorum();
		getTMProxy().rQ = ReadQuorum.getQuorum();
		requestReconfig = new AtomicInteger(0);
		Logger.debug("TM Initiation Completed");
	}
	
	/**
	 * Returns the Object copy if exist in quorum, else returns null
	 */
	public final QObject read(String Id, Long tnx) throws Exception {
		QObject ob = null;
		
//		try {
			/* check in parent's cache */
			Context context = ContextDelegator.getContext(tnx);
			if ((ob=context.checkParentSet(Id)) != null) {
				return ob;
			}
			
			int tries = 0;
			// Might be happening due to packet drop, or object is locked
//			while (ob == null) {
				ob = rQ.read(Id, tnx, true, tries);
//				tries++;
//				if (ob == null) {
//					Logger.debug("Retrying read WTF for "+tnx);
//				}
//			}
			if (ob!= null && ob.getOwner() != 0) {
				Logger.debug("Reading a locked object");
				ob.owner.set(0);
			}
			
			if (ob == null) {
				throw new Exception();
			}
			
//		} catch (QuroumFailureException e) {
//			Logger.debug("Quorum exception for node "+ ClustersManager.baseClusterId+" !!!!");			
//			reconfigCluster();
//			return read(Id, tnx);		//Make Network failure  invisible to application
//		} catch (Exception e) {
//			e.printStackTrace();
//		}

		return ob;
	}
	
	/**
	 * To re-create the rQ, wQ
	 */
	private void reconfigCluster() {
		if(requestReconfig.incrementAndGet() == 1)  // if you are first, reconfigure cluster
			this.config();		//Initiate again to get new clusters
		while(requestReconfig.get() !=0);			//wait until reconfiguration done		
	}

	/**
	 * Returns the Object copy if exist in quorum, else returns null
	 */
	public QObject write(String Id, Long tnx) throws Exception {

		QObject ob = null;
		
//		try {
			/* check in parent's cache */
			Context context = ContextDelegator.getContext(tnx);
				
			if ((ob=context.checkParentSet(Id)) != null) {
				return ob;
			}
//			
			int tries = 0;
//			// Might be happening due to packet drop, or object is locked
////			while (ob == null) {
				ob = rQ.read(Id, tnx, false, tries);
////				tries++;
////				if (ob == null) {
////					Logger.debug("Retrying read WTF for "+tnx);
////				}
////			}
			if (ob!= null && ob.getOwner() != 0) {
				Logger.debug("Reading a locked object");
				ob.owner.set(0);
			}
//			
			ob = null;
			if (ob == null) {
				throw new Exception();
			}
//			return ob;
			
//		QObject ob = null;
//		try {
//			
//			Context context = ContextDelegator.getContext(tnx);
//			if ((ob=context.checkParentWriteSet(Id)) != null) {
//				return ob;
//			}
//			
//			int tries = 0;
////			while (ob == null) {
//				ob = wQ.write(Id, tnx, false, tries);
////				tries++;
////				if (ob == null) {
////					Logger.debug("Retrying write WTF for "+tnx);
////				}
////
////			}
//			
//			if (ob != null && ob.getOwner() != 0) {
//				Logger.debug("Reading a locked object");
//				ob.owner.set(0);
//			}
////			
//			if (ob == null) {
//				throw new Exception();
//			}
////		} catch (QuroumFailureException e) {
////			Logger.debug("Quorum for write exception for node "+ ClustersManager.baseClusterId+" !!!!");
////			reconfigCluster();
////			return write(Id, tnx);		//Make Network failure  invisible to application
////		} catch (Exception e) {
////			e.printStackTrace();
////		}
		return ob;
	}
	
	public boolean requestCommit(Long tnx) throws Exception{
		Boolean b = false;
		int tries = 0;
//		try {
//			while ((b = 
						b = wQ.requestCommit(tnx, tries);
						
						if (b == null) {
							throw new Exception();
						}
//					){
//				tries++;
//				if (b == null) {
//					Logger.debug("Retrying request commit WTF for "+tnx);
//				}				
//			}
//				wQ.requestCommit(tnx);
//		} catch (QuroumFailureException e) {
//			reconfigCluster();
//			return false;			//fail the transaction, TODO: Might be retried to request commit
//		} catch (Exception e) {
//			e.printStackTrace();
//		}
		return b;
	}
	
	public Cluster getReadChannel(){
		return rQ.getReaders();
	}
	
	public Cluster getWriteChannel(){
		return wQ.getWriters();
	}

	/**Publishes the object to the quorum, depending on 
	 * Initialised or uninitialised objects
	 * @param Object
	 */
	public void publishObject(QObject obj) {
		wQ.addToQuorum(obj);		
	}
	
}
